package ua.edu.nuos.tees.jdmmon.vo.implementations;

import ua.edu.nuos.tees.jdmmon.dao.exceptions.CRCException;
import ua.edu.nuos.tees.jdmmon.vo.interfaces.OutputPackage;

/**
 * Implementation of rtu output package
 *
 * @author Olha Mudrievska
 *         22.02.13
 */
public class RtuOutputPackage implements OutputPackage{
    private int deviceAddress = -1;
    private int modbusFunction = -1;
    private int registerAddress = -1;
    private int dataSize = -1;
    private int crc;

    /**
     * Creates a new RtuOutputPackage instance by setting device address,
     * modbus function, register address and data size
     *
     * @param deviceAddress device address
     * @param modbusFunction modbus function
     * @param registerAddress register address
     * @param dataSize number of registers
     */
    public RtuOutputPackage (int deviceAddress, int modbusFunction, int registerAddress, int dataSize) {
        setDeviceAddress(deviceAddress);
        setModbusFunction(modbusFunction);
        setRegisterAddress(registerAddress);
        setDataSize(dataSize);
    }

    /**
     * Creates a new empty RtuOutputPackage instance
     */
    public RtuOutputPackage () {}

    @Override
    public void setDeviceAddress(int tempDeviceAddress) {
        deviceAddress = tempDeviceAddress;
    }

    @Override
    public int getDeviceAddress() {
        return deviceAddress;
    }

    @Override
    public void setModbusFunction(int tempModbusFunction) {
        modbusFunction = tempModbusFunction;
    }

    @Override
    public int getModbusFunction() {
        return modbusFunction;
    }

    @Override
    public void setRegisterAddress(int tempRegisterAddress) {
        registerAddress = tempRegisterAddress;
    }

    @Override
    public int getRegisterAddress() {
        return registerAddress;
    }

    @Override
    public void setDataSize(int tempDataSize) {
        dataSize = tempDataSize;
    }

    @Override
    public int getDataSize() {
        return dataSize;
    }

    @Override
    public int getCRC() {
        crc = calculateCRC();
        return crc;
    }

    @Override
    public byte[] toBytes() {
        crc = calculateCRC();
        byte[] registerAddress = twoBytesIntegerToByteArray(getRegisterAddress());
        byte[] dataSize = twoBytesIntegerToByteArray(getDataSize());
        byte[] crc = twoBytesIntegerToByteArray(getCRC());
        return new byte[] {(byte) deviceAddress, (byte) modbusFunction, registerAddress[0], registerAddress[1],
        dataSize[0], dataSize[1], crc[0], crc[1]};
    }

    /**
     * Convert two bytes integer to bytes array
     * @param twoBytesInteger integer with two bytes
     * @return bytes array with two bytes
     * @throws IllegalArgumentException if param larger then two bytes
     */
    private byte[] twoBytesIntegerToByteArray(int twoBytesInteger) {
        byte firstByte = (byte) (twoBytesInteger >> 8);
        byte secondByte = (byte) twoBytesInteger;
        return new byte[]{firstByte, secondByte};
    }

    /**
     * Calculate CRC of byte array
     * @return crc checksum
     */
    private int calculateCRC() throws CRCException {
        final int STAGES = 7;
        final int POLYNOMIAL = 0xA001;
        if (deviceAddress == -1 || modbusFunction == -1 || registerAddress == -1 || dataSize == -1) {
            throw new CRCException(
            "Some fields are not initialized. For CRC calculation all package data must be set.");
        }
        byte[] registerAddress = twoBytesIntegerToByteArray(getRegisterAddress());
        byte[] dataSize = twoBytesIntegerToByteArray(getDataSize());
        byte[] data = new byte[] {(byte) deviceAddress, (byte) modbusFunction, registerAddress[0], registerAddress[1],
                dataSize[0], dataSize[1]};
        int crc = 0xFFFF;
        for (byte bt : data) {
            crc ^= bt;
            for (int i = 0; i <= STAGES; i++) {
                int beforeShift = crc & 1;
                crc = (crc >> 1);
                crc ^= (beforeShift == 1)? POLYNOMIAL : 0x0;
            }
        }
        crc = (((byte) crc) << 8) +  (byte) (crc >> 8);
        return crc;
    }
}
